//*********************************************************************
//		Copyright (c) 2003 - 2008 Microsoft Corporation.
//      This code is provided under Microsoft Public License, which can be found at
//      http://www.microsoft.com/resources/sharedsource/licensingbasics/publiclicense.mspx 
//
// @File: Target.cs
//
// Purpose: Object controlling collection from target machine
//
//
//
// Notes: 
//
//
//	
// History: 
//     @Version: V2.1
//
//     07/17/08 PGV 2.1.001.0 Starting V2.1 (based on 2.0.030.0 code)
//
//
// @EndHeader@
//*********************************************************************

using System;
using System.Collections;
using System.Data;
using System.Data.SqlClient;
using System.Diagnostics;
using Microsoft.Win32;
using System.Security;


using H2V2.Collector;

namespace H2V2
{
	public class DetectorException : Exception 
	{
		public CollectorError Error;

        public DetectorException (CollectorError a_error, string a_message)
            : base (a_message)
		{ 
			Error = a_error;
		}
	}

    public class Target : StatusObject
	{
		uint m_options = 0xffff; // 0x1 - SQL; 0x2 - Log; 0x4 - WMI;
		public uint Options { get { return m_options; } set { m_options = value; } }

		string m_target_machine; 
		string m_signature;
        string m_ScanDrives;

		ArrayList m_instances_array;
		int m_srv_id;
		RegistryKey m_hklm;

		#region Initialization
		public Target(string a_machine)
		{
			m_target_machine = a_machine;
			m_instances_array = new ArrayList ();
		}

        public Target(string a_machine, string a_ScanDrives)
        {
            m_target_machine = a_machine;
            m_ScanDrives = a_ScanDrives;
            m_instances_array = new ArrayList();
        }

		public void Init ()
		{
			try
			{
				Init_target ();
			}
			catch (DetectorException dex)
			{
				if (dex.Error == CollectorError.DetectorNoInstancesFound) 
				{
					ChangeStatus (dex.Message, StatusEventType.WARNING);
				}
				else throw dex;
			}
			catch (Exception ex)
			{
				ChangeStatus (ex.StackTrace, StatusEventType.STACK_TRACE);
				throw ex;
			}
		}

		void Init_target ()
		{
			ArrayList instances_array = new ArrayList (1);
			int q_running = 0;

			ChangeStatus ("Initializing Target: "+m_target_machine);

			if (m_options != 0xffff)
			{
				ChangeStatus ("Tagret is running with non-default options 0x"+m_options.ToString ("X"), StatusEventType.WARNING);
			}

			ChangeStatus ("  registering Server");
			RegisterServer ();

			ScanRegistry (instances_array);

			foreach (string inst_name in instances_array)
			{
				SqlInstance inst = new SqlInstance ();
				inst.inst_name = inst_name;
				inst.name = CollectorBase.ConstructConnectionName (m_target_machine, inst_name);

				inst.IsDefault = (inst_name.ToUpper () == "MSSQLSERVER") ? 1 : 0;

				ChangeStatus ("  registering instance "+inst.name);
		
				inst.cn = new Connection (ConnectionType.TARGET);
				inst.cn.OnStatusChangeHandler += new Connection.StatusChangeEventHandler(OnStatusChange);
				try 
				{
					inst.cn.Init (inst.name, null);
					if (CmdLineArg.bCustomDateFormat)
					{
						inst.cn.StoreNonQuery ("SET DATEFORMAT "+CmdLineArg.CustomDateFormat);
					}
					inst.IsRunning = true;
					++q_running;
				}
				catch (StorageErrorException serr)
				{
					inst.IsRunning = false;

					LogStorError (serr, inst.inst_id);
					ChangeStatus ("Instance "+inst.name+" is not presently running or unavailable. "+
						"SQL Collector will not attempt connecting to the instance", StatusEventType.WARNING);
					GetVersion (ref inst);
				}
					
				inst.inst_id = RegisterInstance (m_srv_id, inst.name, inst.IsDefault);
				ChangeStatus ("version: "+inst.cn.Version.version, StatusEventType.INFO);
				m_instances_array.Add (inst);
			}

			if (q_running == 0)
			{
				ChangeStatus ("No running SQL Server instances on the box", StatusEventType.WARNING);
			}
		}

		#endregion

		#region Registry scanning functions
		void ScanRegistry (ArrayList a_list)
		{
            RegistryKey rk, rk32;

			CollectorError error_id = CollectorError.UnexpectedException;

			ChangeStatus ("Scanning Registry on "+m_target_machine);			

			try
			{
				m_hklm = RegistryKey.OpenRemoteBaseKey (RegistryHive.LocalMachine, m_target_machine);
			}
			catch (Exception ex)
			{
				string xtype = ex.GetType ().Name;
				switch (xtype)
				{
					case "ArgumentException":
						error_id = CollectorError.RegistryInvalidArgument;
						break;
					case "IOException":
						error_id = CollectorError.RegistryPathNotFound;
						break;
					case "UnauthorizedAccessException":
						error_id = CollectorError.RegistryUnauthorizedAccessException;
						break;
					case "SecurityException":
						error_id = CollectorError.RegistrySecurityException;
						break;
				}
				H2Manager.LogMgrError (m_srv_id, 0, RunErrorClass.Server, 0, xtype, error_id, 0, ex.Message);

                if (error_id > 0)
                {
                    throw new DetectorException (error_id, ex.Message);
                }
                else
                {
                    throw ex;
                }
			}

            
			try
			{
				rk = m_hklm.OpenSubKey ("SOFTWARE\\Microsoft\\Microsoft SQL Server", false);
			}
			catch (Exception ex)
			{
                H2Manager.LogMgrError (m_srv_id, 0, RunErrorClass.Server, 0, ex.GetType ().Name, CollectorError.RegistrySecurityExceptionAccessingSql, 0, ex.Message);
                throw new DetectorException (CollectorError.RegistrySecurityExceptionAccessingSql, ex.Message);
			}

            bool InstancesExist = true;
			try
			{
                //http://msdn.microsoft.com/en-us/library/a6t1z9x2.aspx
				foreach (string iname in (String [])rk.GetValue ("InstalledInstances"))
				{
					a_list.Add (iname);
				}
			}
			catch (NullReferenceException) 
			{
                InstancesExist = false;
			}

            //get any 32 bit versions installed on 64 bit machine
            try
            {
                rk32 = m_hklm.OpenSubKey("SOFTWARE\\Wow6432Node\\Microsoft\\Microsoft SQL Server", false);
            }
            catch (Exception ex)
            {
                H2Manager.LogMgrError(m_srv_id, 0, RunErrorClass.Server, 0, ex.GetType().Name, CollectorError.RegistrySecurityExceptionAccessingSql, 0, ex.Message);
                throw new DetectorException(CollectorError.RegistrySecurityExceptionAccessingSql, ex.Message);
            }

            try
            {
                //http://msdn.microsoft.com/en-us/library/a6t1z9x2.aspx
                foreach (string iname in (String[])rk32.GetValue("InstalledInstances"))
                {
                    a_list.Add(iname);
                }
            }
            catch (NullReferenceException)
            {
                if (!InstancesExist)
                {
                    throw new DetectorException(CollectorError.DetectorNoInstancesFound, "No SQL Server instances registered on the box");
                }
            }
		}

		void GetVersion (ref SqlInstance a_inst)
		{
			Version v = getRegistryVersion (a_inst.inst_name);
			a_inst.cn.SetVersion (v);
		}

		Version getRegistryVersion (string a_inst_name)
		{
			string s_key,
				ver = "0.0.0";
			RegistryKey vkey;
			Version v;

			ChangeStatus ("Trying to obtain SQL Server version form the registry");

			s_key = CollectorBase.GetInstanceKey (m_hklm, a_inst_name, H2ProductLevel.Sql2000SP3);

			try
			{
				s_key = s_key + "\\MSSQLServer\\CurrentVersion";
				vkey = m_hklm.OpenSubKey (s_key);
				if (vkey != null)
				{
					ver = (string)vkey.GetValue ("CurrentVersion");
					if (ver != null)
					{
						if (ver.StartsWith ("8"))
						{
							ver = (string)vkey.GetValue ("CSDVersion");
						}
					}
				}
				else
				{
					s_key = CollectorBase.GetInstanceKey (m_hklm, a_inst_name, H2ProductLevel.Sql2005); // covers Sql2008 as well
					s_key = s_key + "\\MSSQLServer\\CurrentVersion";
					vkey = m_hklm.OpenSubKey (s_key);
					if (vkey != null)
					{
						ver = (string)vkey.GetValue ("CurrentVersion");
					}
				}
			}
			catch (Exception ex)
			{
				ChangeStatus (ex.Message, StatusEventType.ERROR);
				ChangeStatus ("Unable to obtain version from the registry, default to PreSql2000SP3", StatusEventType.WARNING);
			}

			v = new Version (ver);

			return v; 
		}

		#endregion

		#region Registration functions (Server, Instance, COllector)
		void RegisterServer ()
		{
			SqlCommand rcmd;
			SqlParameter p_name;
			SqlParameter p_IsNew;
			SqlParameter p_srv_id;
			SqlParameter p_signature;
			SqlParameter retval;
			int retcode;


			rcmd = new SqlCommand ("dbo.spr_server", H2Manager.Repository.SQLConnection);
			rcmd.CommandType = CommandType.StoredProcedure;

			retval = new SqlParameter ("retvalue", SqlDbType.Int);
			retval.Direction = ParameterDirection.ReturnValue;
			rcmd.Parameters.Add (retval);
			p_name = new SqlParameter ("@name", SqlDbType.NVarChar);
			p_name.Direction = ParameterDirection.Input;
			p_name.Value = this.m_target_machine;
			rcmd.Parameters.Add (p_name);
			p_IsNew = new SqlParameter ("@IsNew", SqlDbType.Int);
			p_IsNew.Direction = ParameterDirection.Input;
			p_IsNew.Value = 1;   // Pretend it's new (even if it's not)
			rcmd.Parameters.Add (p_IsNew);
			p_srv_id = new SqlParameter ("@srv_id", SqlDbType.Int);
			p_srv_id.Direction = ParameterDirection.Output;
			rcmd.Parameters.Add (p_srv_id);
			p_signature = new SqlParameter ("@signature", SqlDbType.UniqueIdentifier);
			p_signature.Direction = ParameterDirection.Output;
			rcmd.Parameters.Add (p_signature);

			rcmd.ExecuteNonQuery ();
			try 
			{
				retcode = (int)retval.Value;
				m_srv_id = (int)p_srv_id.Value;
				m_signature = p_signature.Value.ToString ();
				if (retcode != 0)
				{
					if (retcode == -200) // new registration fails - name already exists
					{ // Do nothing; 
					}
					else
					{
                        throw (new StorageErrorException (CollectorError.StorSpError, 0, "dbo.spr_server"));
					}
				}
				ChangeStatus ("srv_id = "+m_srv_id+"; signature = "+m_signature, StatusEventType.INFO);
			}
			catch (SqlException sqlex)
			{
				H2Manager.Log.SQLException (sqlex);
				ChangeStatus (sqlex.StackTrace, StatusEventType.STACK_TRACE);
				throw (new StorageErrorException (CollectorError.StorSyntaxErrorReaderSP,sqlex.Number,"dbo.spr_server"));
			}
		}
        
		short RegisterInstance (int a_srv_id, string a_name, int a_default)
		{
			short inst_id;
			SqlCommand rcmd;
			SqlParameter p_inst_id;
			SqlParameter retval;
			int retcode;


			rcmd = new SqlCommand ("dbo.spr_instance", H2Manager.Repository.SQLConnection);
			rcmd.CommandType = CommandType.StoredProcedure;

			retval = new SqlParameter ("retvalue", SqlDbType.Int);
			retval.Direction = ParameterDirection.ReturnValue;
			rcmd.Parameters.Add (retval);
            rcmd.Parameters.AddWithValue ("@srv_id", a_srv_id);
            rcmd.Parameters.AddWithValue ("@name", a_name);
            rcmd.Parameters.AddWithValue ("@IsDefault", a_default);
			p_inst_id = new SqlParameter ("@inst_id", SqlDbType.SmallInt);
			p_inst_id.Direction = ParameterDirection.Output;
			rcmd.Parameters.Add (p_inst_id);

			rcmd.ExecuteNonQuery ();
			try 
			{
				retcode = (int)retval.Value;
				inst_id = (short)p_inst_id.Value;
				if (retcode != 0)
				{
					if (retcode == -200) // new registration fails - name already exists
					{ // Do nothing; 
					}
					else
					{
                        throw (new StorageErrorException (CollectorError.StorSpError, 0, "dbo.spr_instance"));
					}
				}
				ChangeStatus ("inst_id = "+inst_id, StatusEventType.INFO);
			}
			catch (SqlException sqlex)
			{
				H2Manager.Log.SQLException (sqlex);
				ChangeStatus (sqlex.StackTrace, StatusEventType.STACK_TRACE);
				throw (new StorageErrorException (CollectorError.StorSyntaxErrorReaderSP,sqlex.Number,"dbo.spr_instance"));
			}

			return inst_id;
		}

		void RegisterCollector (CollectorBase a_collector, short a_index)
		{
			int c_id;
			uint options;
			string s_rcmd;

			options = a_collector.Options;
			c_id = a_collector.CollectorID;

			s_rcmd = "Insert dbo.mgr_run_detail(run_id, c_id, options, srv_id, inst_id) Values("+
				H2Manager.RunID+","+c_id+", "+options+","+m_srv_id+", "+a_index+")";
			H2Manager.Repository.StoreNonQuery (s_rcmd);

		}
		#endregion

		void LogStorError (StorageErrorException serr, short inst_id)
		{
            CollectorError error_id;
            int aux_error_id = 0;
			string obj_name, 
				msg;

			error_id = serr.m_Error;
			aux_error_id = serr.m_auxError;
			if (serr.InnerException != null)
			{
				obj_name = serr.Message;
				msg = serr.InnerException.Message;
			}
			else
			{
				obj_name = "";
				msg = serr.Message;
			}

			H2Manager.LogMgrError (m_srv_id, inst_id, RunErrorClass.SqlServer, 0, obj_name, error_id, aux_error_id, msg);
		}

		public void Run ()
		{
			short state = 0;
			CollectorSql isql;
			CollectorWmi wmi;
			CollectorEventLog log;
            
			ChangeStatus ("  RUN  ");
			
			try
			{
				Connection conn = H2Manager.Repository;

                //run the wmi collector
				RunDescriptor rd0 = new RunDescriptor (m_srv_id, 0, H2Manager.RunID);
				if ((m_options & 0x4) > 0)
				{
					wmi = new CollectorWmi (m_target_machine, rd0);
					wmi.OnStatusChangeHandler += new CollectorBase.StatusChangeEventHandler (OnStatusChange);
					if (CmdLineArg.bDebugCollectorWmiOptions) 
					{
						wmi.Options = CmdLineArg.DebugCollectorWmiOptions;
					}
					wmi.Init (conn);
					H2Manager.UpdateRunState (1);
					RegisterCollector (wmi, 0);
					wmi.Collect ();
					H2Manager.UpdateRunState (2);
				}

                //init the log collector
				log = new CollectorEventLog (m_target_machine, rd0);
				log.OnStatusChangeHandler += new CollectorBase.StatusChangeEventHandler (OnStatusChange);
				if ((m_options & 0x2) > 0)
				{
					if (CmdLineArg.bDebugCollectorLogOptions) 
					{
						log.Options = CmdLineArg.DebugCollectorLogOptions;
					}
					log.Init (conn);
				}
		
                //run sql collector
				state = 10;
				foreach (SqlInstance sti in m_instances_array)
				{
					if (sti.cn.ProductLevel < H2ProductLevel.Sql2000SP3)
					{
						ChangeStatus ("Instance "+sti.name+" has level '"+sti.cn.ProductLevel+"'");
						ChangeStatus ("version: "+sti.cn.Version.version, StatusEventType.INFO);
						ChangeStatus ("H2 supports SQL Server 2000 SP3 and higher only. No SQL-related information will be collected.", StatusEventType.WARNING);
						continue;
					}

					RunDescriptor rd = new RunDescriptor (m_srv_id, sti.inst_id, H2Manager.RunID);

					if ((m_options & 0x1) > 0)
					{
						isql = new CollectorSql (m_target_machine, sti.inst_name, rd);
						isql.OnStatusChangeHandler += new CollectorBase.StatusChangeEventHandler (OnStatusChange);
						if (CmdLineArg.bDebugCollectorSqlOptions) 
						{
							isql.Options = CmdLineArg.DebugCollectorSqlOptions;
						}
						isql.Init (conn, sti, m_hklm);
						H2Manager.UpdateRunState (++state);
						RegisterCollector (isql, sti.inst_id);
						isql.Collect ();
						H2Manager.UpdateRunState (++state);
					}

					if ((m_options & 0x2) > 0) log.AddSource (sti.inst_name, sti.inst_id, sti.cn.Version);
				}

				if ((m_options & 0x2) > 0)
				{
					H2Manager.UpdateRunState (81);
					RegisterCollector (log, 0);
					log.Collect ();
					H2Manager.UpdateRunState (82);
				}

				H2Manager.UpdateRunState (100);
				ChangeStatus (this.m_target_machine+": Collection finished");
			}
			catch (DetectorException dex)
			{
				ChangeStatus (dex.Message);
				throw (new Exception (this.m_target_machine+": Collection failed"));
			}
			catch (CollectorErrorException cerr)
			{
				string msg = cerr.Message;
				ChangeStatus (cerr.m_Error+" "+cerr.Message, StatusEventType.ERROR);
				
				ChangeStatus (cerr.StackTrace, StatusEventType.STACK_TRACE);
                if (cerr.m_Error == CollectorError.AccessDenied)
                {
                    msg = this.m_target_machine + ": " + cerr.Message + " - Access Denied";
                }
				throw (new Exception (msg));
			}
			catch (StorageErrorException serr)
			{
				ChangeStatus (serr.Message, StatusEventType.ERROR);
				ChangeStatus (this.m_target_machine+": Collection failed;  Error:"+serr.m_Error
					+";  Provider Error: "+serr.m_auxError, StatusEventType.ERROR);
				ChangeStatus (serr.StackTrace, StatusEventType.STACK_TRACE);
				throw (new Exception (this.m_target_machine+": Collection failed"));
			}
			catch (Exception e)
			{
				ChangeStatus (e.Message, StatusEventType.ERROR);
				ChangeStatus (e.StackTrace, StatusEventType.STACK_TRACE);
				H2Manager.Log.Flush ();
				throw new Exception (this.m_target_machine+": Collection failed", e);
			}

		}
	}
}
